Time synchronization under Windows
I thought that this topic could be interesting for some new coders. A good synchronization in your demo or intro is a key to success (or should be :-)). In this article I will show you some of the common techniques used by demo coders.
First of all, I hope that you know what synchronization stands for. :-). Generally speaking it means that demo/intro (that you coded) will be executed on various PCs during this same period of time. On the other hand this mechanism will help you with music and screen synchronization.
Let's go back to coding aspect of synchronization. If you are basically familiar with Windows programming, you know probably almost everything about Windows timers, which are based on WM_TIMER message. These methods are not usable in real-time coding (accuracy is far from perfect). Also you can use the GetTickCount() standard function but it's not so good as Windows Multimedia Timers or High-Resolution Timer. Both of these methods (and a few others) we will discuss in this article. I must admit that all presented topics can be found in MSDN (or at M$ www site).
In the first order, we get High-Resolution Timers on the top, which are the easiest (?) in implementation and the most precise ones. We will discuss two functions: the QueryPerformanceFrequency and the QueryPerformanceCounter. I must admit that installed hardware must support a high-resolution performance counter (check the error codes that are returned by functions).
- The QueryPerformanceFrequency function retrieves the frequency of the high-resolution performance counter, if one exists.
- The QueryPerformanceCounter function retrieves the current value of the high-resolution performance counter.
Now, when you know everything about HRT I will give you a small example (just an exampl!). I assume that we do synchronization to the lost frames.
//
// The first listing.
// Using High-Resolution Timer.
//
// our render function
void RenderProcedure( void )
{
static bool bFirstTime = true; // just a simple flag
static LONGLONG llQPFTicksPerSec = 0; // frequency of HRT
static LONGLONG llLastElapsedTime = 0; // last elapsed time...
static LARGE_INTEGER qwTime; // you will see...
// first time initialising
if( bFirstTime )
{
bFirstTime = false;
LARGE_INTEGER qwTicksPerSec; // !! LARGE_int
// Use QueryPerformanceFrequency() to get frequency of timer.
// If QPF is not supported, we will discusse it next...
if( !QueryPerformanceFrequency( &qwTicksPerSec ) )
throw "No QPT"; // or smth...
llQPFTicksPerSec = qwTicksPerSec.QuadPart;
}
// Now, get "time"
QueryPerformanceCounter( &qwTime );
// calculate the current elapsed time
double fElapsedTime = (double) ( qwTime.QuadPart - llLastElapsedTime )
/ (double) llQPFTicksPerSec;
if( fElapsedTime < 1.0 ) // one second
return;
// set new 'last elapsed time'
llLastElapsedTime = qwTime.QuadPart;
//
// Do smth.... the frame (or frames!!) has/have skipped since last call
//
}
Now it is time for Multimedia Timers! First we will discuss the timeGetTime function, which is quite similar to the QueryPerformanceCounter function. The timeGetTime function retrieves the system time, in milliseconds. The system time is the time elapsed since Windows has started, therefore there could be the situation that our counter will exceed the maximum value (after 49 days...). An example? No problem Sir.
//
// Listing #2
// Using 'timeGetTime' function (winmm.lib is needed).
//
void RenderProcedure( void )
{
static DWORD dwLastElapsedTime = 0;
static DWORD dwTime;
static DWORD dwElapsedTime;
dwTime = timeGetTime(); // get current "time"
dwElapsedTime = dwTime - dwLastElapsedTime; // calculate elapsed time
// check how many milliseconds have there passed
if( dwElapsedTime < 25 ) // 40 fps
return;
dwLastElapsedTime = dwTime; // save current time as last elapsed time
//
// time has passed...
//
}
In this paragraph we will discuss the most interesting aspect of MTs (IMHO :-)). We will set our own counter in a separated thread. Don't worry! MT's API does everything for us. Functions that we will use are: timeBeginPeriod, timeSetEvent, timeKillEvent, timeEndPeriod and timeGetDevCaps. The whole trick is that our procedure will be called periodically after expected time. In that procedure we will simply increase our counter (which is a volatile variable). It's time for an example (a Windows console application).
//
// Listing #3
// (c) 2001 by Turms/.exiLe
// This is an console aplication. Add winmm.lib to project.
//
#include <windows.h>
#include <mmsystem.h>
#include <iostream.h>
#include <conio.h>
#define TIMER_RESOLUTION 2 // 2-millisecond target resolution
//
// global var.
//
volatile DWORD dwFrames;
//
// our "frame counter"
//
void CALLBACK FrameCounter( UINT wTimerID, UINT msg,
DWORD dwUser, DWORD dw1, DWORD dw2 )
{
++dwFrames;
// of course in real app. you shouldn't write like this:
cout << dwFrames << endl;
}
//
// - MAIN -
//
int main( void )
{
UINT wTimerRes; // timer resolturion
UINT wTimerID; // timer ID
TIMECAPS timecaps; // needed by timeGetDevCaps
// get max & min of sys timer
if ( timeGetDevCaps( &timecaps, sizeof( TIMECAPS ) ) != TIMERR_NOERROR )
{
cout << "Error, application can't continue" << endl;
return 0;
}
// get optimal resolution
wTimerRes = max( timecaps.wPeriodMin, TIMER_RESOLUTION );
// set minimal res for our timer
if( timeBeginPeriod( wTimerRes ) != TIMERR_NOERROR )
{
// error goes here
}
// Now, run the timer
wTimerID = timeSetEvent(
500, // delay in milis. - half a sec.
wTimerRes, // resolution
FrameCounter, // callback function
NULL, // user data
TIME_PERIODIC ); // periodic timer event
if( wTimerID == NULL )
{
// error
}
// do nothing in the main thread
while( !kbhit() );
// kill the timer
timeKillEvent( wTimerID );
wTimerID = 0;
timeEndPeriod( wTimerRes ); // return previous setings
return 0;
}
The last method of synchronization that I'll discuss isn't directly connected with the Windows API. When you use e.g. FMOD music player, you can use its API. Check the documentation for details.
Apart from this article you should read about the following topics related to timing under Windows. All of these texts you can easily find in MSDN. Interesting articles and topics are:
- Availability of Multimedia Timers
- High-Precision Timing Under Windows, Windows NT, & Windows 95
- High-Resolution Timer
- SAMPLE: How to Use Multimedia Timer Services on Windows 95
- Using Multimedia Timers.
Any opinions? Write to: adam007@box43.pl
Turms/.exiLe
www.exile.prv.pl (check it now! :-))